/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sle_connect_manager.h"
#include "sle_adapter_manager.h"
#include "sle_hilog.h"

namespace OHOS {
namespace NearLink {
namespace SleStandard {
SleConnectManager::SleConnectManagerImpl::SleConnectManagerImpl()
{
    connService_ = SleConnectService::GetInstance();
    SLE_ASSERT_AND_RETURN_LOG(connService_, "connect service instance null error!");
    callbackImpl_ = std::make_unique<ConnectCallbackImplement>(*this);
    SLE_ASSERT_AND_RETURN_LOG(callbackImpl_, "callbackImpl null error!");
    SleErrorCode ret = connService_->RegisterConnectCallback(*callbackImpl_.get());
    SLE_ASSERT_AND_RETURN_LOG(ret == SLE_NO_ERROR, "register connect callback failed!");
}

SleConnectManager::SleConnectManagerImpl::~SleConnectManagerImpl()
{
    SLE_ASSERT_AND_RETURN_LOG(connService_, "connect service instance null error!");
    SLE_ASSERT_AND_RETURN_LOG(callbackImpl_, "callbackImpl null error!");
    SleErrorCode ret = connService_->UnregisterConnectCallback(*callbackImpl_.get());
    SLE_ASSERT_AND_RETURN_LOG(ret == SLE_NO_ERROR, "unregister connect callback failed!");
}

SleErrorCode SleConnectManager::SleConnectManagerImpl::DoConnect(Device &device)
{
    SLE_LOGD("DoConnect In");
    SLE_ASSERT_AND_RETURN_RET_LOG(connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(device.Tm(), SLE_ERR_NOT_READY, "device timer error!");
    device.Tm()->Start(SLE_CONNECT_TIMEOUT, false);
    SleErrorCode ret = connService_->Connect(device.GetSleAddress());
    if (ret != SLE_NO_ERROR) {
        if (device.Tm() != nullptr) {
            device.Tm()->Stop();
        }
        SLE_LOGE("do connect failed!");
        return ret;
    }
    device.Role() = CONNECT_ROLE_MASTER; //发起方
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::SleConnectManagerImpl::DoConnectComplete(Device &device, const Message &msg)
{
    SLE_LOGD("DoConnectComplete In");
    if (device.Role() == CONNECT_ROLE_MASTER) {
        if (device.Tm() != nullptr) {
            device.Tm()->Stop();
        }
    }
    if (((SleConnectCbInfo *)msg.arg2_)->state == SLE_CONNECT_CONNECTED_STATE) {
        device.ConnId() = msg.arg1_;
        return SLE_NO_ERROR;
    } else {
        return SLE_ERR_FAILED;
    }
}

SleErrorCode SleConnectManager::SleConnectManagerImpl::DoDisconnect(Device &device)
{
    SLE_LOGD("DoDisconnect In");
    SLE_ASSERT_AND_RETURN_RET_LOG(connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    SleErrorCode ret = connService_->Disconnect(device.GetSleAddress());
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "do disconnect failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::SleConnectManagerImpl::DoDisconnectComplete(Device &device, const Message &msg)
{
    SLE_LOGD("DoDisconnectComplete In");
    if (((SleConnectCbInfo *)msg.arg2_)->state == SLE_CONNECT_DISCONNECTED_STATE) {
        device.ConnId() = msg.arg1_;
        if (((SleConnectCbInfo *)msg.arg2_)->pairState != SLE_PAIR_NONE_STATE) {
            SleErrorCode ret = connService_->RemovePairedDevice(device.GetSleAddress());
            SLE_ASSERT_AND_PRINT_LOG(ret == SLE_NO_ERROR, "disconnected remove paired device failed(%hhu)!", ret);
        }
        return SLE_NO_ERROR;
    } else {
        return SLE_ERR_FAILED;
    }
}

SleErrorCode SleConnectManager::SleConnectManagerImpl::DoUpdateConnectParam(Device &device, const Message &msg)
{
    SLE_LOGD("DoUpdateConnectParam In");
    SLE_ASSERT_AND_RETURN_RET_LOG(connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(device.Tm(), SLE_ERR_NOT_READY, "device timer error!");
    device.Tm()->Start(SLE_CONNECT_UPDATE_PARAM_TIMEOUT, false);
    SleErrorCode ret = connService_->UpdateConnectParam(msg.arg1_, (SleConnectParam *)(msg.arg2_));
    if (ret != SLE_NO_ERROR) {
        if (device.Tm() != nullptr) {
            device.Tm()->Stop();
        }
        return ret;
    }
    return SLE_NO_ERROR;
}

Device* SleConnectManager::SleConnectManagerImpl::FindOrRegister(const SleAddress address)
{
    SLE_LOGD("FindOrRegister In");
    std::lock_guard<std::mutex> lock(lock_);
    for (auto &dev : devices_) {
        if (dev->GetSleAddress() == address) {
            SLE_LOGD("find device");
            return dev.get();
        }
    }
    if (devices_.size() < SLE_MAX_CONNECT_ID_NUM) {
        devices_.emplace_back(std::make_unique<Device>(address));
        SLE_LOGD("add device");
        return devices_.back().get();
    } else {
        SLE_LOGE("the maximum number of connections has been reached error!");
        return nullptr;
    }
}

Device* SleConnectManager::SleConnectManagerImpl::FindOrRegister(uint16_t connectId, const SleAddress address)
{
    SLE_LOGD("FindOrRegister In");
    std::lock_guard<std::mutex> lock(lock_);
    for (auto &dev : devices_) {
        if (dev->GetSleAddress() == address || dev->ConnId() == connectId) {
            SLE_LOGD("find device, connectId: %{public}X", connectId);
            return dev.get();
        }
    }
    if (devices_.size() < SLE_MAX_CONNECT_ID_NUM) {
        devices_.emplace_back(std::make_unique<Device>(address, connectId));
        SLE_LOGD("add device, connectId: %{public}X", connectId);
        return devices_.back().get();
    } else {
        SLE_LOGE("the maximum number of connections has been reached error!");
        return nullptr;
    }
}

Device* SleConnectManager::SleConnectManagerImpl::FindDevice(const SleAddress address)
{
    SLE_LOGD("FindDevice In");
    std::lock_guard<std::mutex> lock(lock_);
    for (auto &dev : devices_) {
        if (dev->GetSleAddress() == address) {
            SLE_LOGD("find device");
            return dev.get();
        }
    }
    return nullptr;
}

Device* SleConnectManager::SleConnectManagerImpl::FindDevice(uint16_t connectId)
{
    SLE_LOGD("FindDevice In");
    std::lock_guard<std::mutex> lock(lock_);
    for (auto &dev : devices_) {
        if (dev->ConnId() == connectId) {
            SLE_LOGD("find device, connectId: %{public}X", connectId);
            return dev.get();
        }
    }
    return nullptr;
}

void SleConnectManager::SleConnectManagerImpl::GetConnectDeviceList(
    std::vector<SleConnectDevice> &devices, uint16_t &number)
{
    std::lock_guard<std::mutex> lock(lock_);
    SleConnectDevice device;
    for (auto &dev : devices_) {
        device.connectId_ = dev->ConnId();
        device.address_ = dev->GetSleAddress();
        devices.emplace_back(device);
    }
    number = devices.size();
}

void SleConnectManager::SleConnectManagerImpl::RemoveDevice(Device &device)
{
    SLE_LOGD("RemoveDevice In");
    std::lock_guard<std::mutex> lock(lock_);
    for (auto dev = devices_.begin(); dev != devices_.end(); ++dev) {
        if ((*dev)->GetSleAddress() == device.GetSleAddress()) {
            devices_.erase(dev);
            return;
        }
    }
}

void SleConnectManager::SleConnectManagerImpl::ConnectionInComming(uint16_t connectId, const SleAddress &address,
    SleConnectState state, SlePairState pairState, SleDisConnectReason reason)
{
    SLE_LOGD("ConnectionInComming In");
    auto internalDevice = FindOrRegister(address);
    SLE_ASSERT_AND_RETURN_LOG(internalDevice, "find or register device failed!");

    SleConnectCbInfo data = {
        .state = state,
        .pairState = pairState,
        .reason = reason
    };
    internalDevice->SetConnectCbInfo(data);
    if (state == SLE_CONNECT_CONNECTED_STATE) {
        Message msg(SleConnectStateMessage::MSG_CONNECT_CMP, (int)connectId, &data);
        internalDevice->SM()->ProcessMessage(msg);
    } else if (state == SLE_CONNECT_DISCONNECTED_STATE) {
        Message msg(SleConnectStateMessage::MSG_DISCONNECT_CMP, (int)connectId, &data);
        internalDevice->SM()->ProcessMessage(msg);
    } else {
        return;
    }
}

void SleConnectManager::SleConnectManagerImpl::ConnectParamReqInComming(uint16_t connectId,
    const SleConnectParam &param, SleErrorCode errCode)
{
    SLE_LOGD("ConnectParamReqInComming In");
    if (errCode != SLE_NO_ERROR) {
        auto internalDevice = FindDevice(connectId);
        SLE_ASSERT_AND_RETURN_LOG(internalDevice, "find or register device failed!");
        SleConnectionParamUpdateEvt event = {
            .interval = 0xFF,
            .latency = param.latency,
            .supervisionTimeout = param.supervisionTimeout
        };
        SleConnectParamCompleteCbInfo data = {
            .errCode = errCode, // Request failed
            .param = event
        };
        Message msg(SleConnectStateMessage::MSG_PARAM_UPDATE_RSP, (int)connectId, &data);
        internalDevice->SM()->ProcessMessage(msg);
    }
}

void SleConnectManager::SleConnectManagerImpl::ConnectParamRspInComming(uint16_t connectId,
    const SleConnectionParamUpdateEvt &param, SleErrorCode errCode)
{
    SLE_LOGD("ConnectParamRspInComming In");
    auto internalDevice = FindDevice(connectId);
    SLE_ASSERT_AND_RETURN_LOG(internalDevice, "find or register device failed!");
    SleConnectParamCompleteCbInfo data = {
        .errCode = errCode,
        .param = param
    };
    internalDevice->SetConnectParamInfo(data);
    Message msg(SleConnectStateMessage::MSG_PARAM_UPDATE_RSP, (int)connectId, &data);
    internalDevice->SM()->ProcessMessage(msg);
}

void SleConnectManager::SleConnectManagerImpl::OnConnectStateChange(Device &device, SleConnectState state)
{
    SLE_LOGD("OnConnectStateChange In");
    device.SetConnectState(state);
    callbacks_.ForEach([&device, state](ISleConnectManagerCallback &callback) {
        callback.OnConnectStateChange(device.ConnId(), device.GetSleAddress(), state,
            device.GetConnectCbInfo().pairState, device.GetConnectCbInfo().reason);
    });
    if (state == SLE_CONNECT_DISCONNECTED_STATE) {
        RemoveDevice(device);
    }
}

void SleConnectManager::SleConnectManagerImpl::OnConnectParamUpdateComplete(Device &device)
{
    SLE_LOGD("OnConnectParamUpdateComplete In");
    SLE_ASSERT_AND_RETURN_LOG(device.Tm(), "device timer error!");
    device.Tm()->Stop();
    callbacks_.ForEach([&device](ISleConnectManagerCallback &callback) {
        callback.OnConnectParamUpdateComplete(
            device.ConnId(), device.GetConnectParamInfo().param, device.GetConnectParamInfo().errCode);
    });
}

// SleConnectManager
SleConnectManager::SleConnectManager()
{
    SLE_LOGD("SleConnectManager In");
    pimpl_ = std::make_unique<SleConnectManagerImpl>();
    SLE_ASSERT_AND_RETURN_LOG(pimpl_, "pimpl null error!");
}

SleConnectManager::~SleConnectManager() {}

SleConnectManager *SleConnectManager::GetInstance()
{
    static SleConnectManager instance;
    return &instance;
}

SleErrorCode SleConnectManager::Connect(const SleAddress &address) const
{
    SLE_LOGD("Connect In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_->connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    auto internalDevice = pimpl_->FindOrRegister(address);
    SLE_ASSERT_AND_RETURN_RET_LOG(internalDevice, SLE_ERR_NOT_READY, "find or register device failed!");
    SleConnectState state = internalDevice->GetConnectState();
    if (state == SLE_CONNECT_CONNECTED_STATE) {
        return SLE_ERR_CONNECT_SUCCESS_WHEN_CONNECTED;
    }
    if (!internalDevice->SM()->ProcessMessage(SleConnectStateMessage::MSG_CONNECT)) {
        return SLE_ERR_FAILED;
    }
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::Disconnect(const SleAddress &address) const
{
    SLE_LOGD("Disconnect In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_->connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    auto internalDevice = pimpl_->FindOrRegister(address);
    SLE_ASSERT_AND_RETURN_RET_LOG(internalDevice, SLE_ERR_NOT_READY, "find or register device failed!");
    SleConnectState state = internalDevice->GetConnectState();
    if (state == SLE_CONNECT_DISCONNECTED_STATE) {
        return SLE_ERR_DISCONNECT_SUCCESS_WHEN_DISCONNECTED;
    }
    if (!internalDevice->SM()->ProcessMessage(SleConnectStateMessage::MSG_DISCONNECT)) {
        return SLE_ERR_FAILED;
    }
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::GetConnectId(const SleAddress &address, uint16_t &connectId) const
{
    SLE_LOGD("GetConnectId In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_->connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    auto internalDevice = pimpl_->FindDevice(address);
    SLE_ASSERT_AND_RETURN_RET_LOG(internalDevice, SLE_ERR_NOT_READY, "no device error!");
    connectId = internalDevice->ConnId();
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::GetConnectState(const SleAddress &address, int32_t &state) const
{
    SLE_LOGD("GetConnectState In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_->connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    auto internalDevice = pimpl_->FindDevice(address);
    if (!internalDevice) {
        state = SLE_CONNECT_DISCONNECTED_STATE;
    } else {
        state = internalDevice->GetConnectState();
    }
    SLE_ASSERT_AND_RETURN_RET_LOG(internalDevice, SLE_ERR_NOT_READY, "no device error!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::GetConnectDeviceList(std::vector<SleConnectDevice> &devices, uint16_t &number) const
{
    SLE_LOGD("GetConnectState In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_->connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    pimpl_->GetConnectDeviceList(devices, number);
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::UpdateConnectParam(uint16_t connectId, const SleConnectionParam &param) const
{
    SLE_LOGD("UpdateConnectParam In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_->connService_, SLE_ERR_NOT_READY, "connect service instance null error!");
    auto internalDevice = pimpl_->FindDevice(connectId);
    SLE_ASSERT_AND_RETURN_RET_LOG(internalDevice, SLE_ERR_NOT_READY, "find or register device failed!");
    SleConnectParam connParam;
    param.ConvertToSleConnectParam(connParam);
    Message msg(SleConnectStateMessage::MSG_PARAM_UPDATE_REQ, (int)connectId, &connParam);
    if (internalDevice->SM()->ProcessMessage(msg) == false) {
        return SLE_ERR_FAILED;
    }
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::RegisterConnectCallback(ISleConnectManagerCallback &callback) const
{
    SLE_LOGD("RegisterConnectCallback In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(
        pimpl_->callbacks_.Register(callback), SLE_ERR_FAILED, "register connect callback failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::UnregisterConnnectCallback(ISleConnectManagerCallback &callback) const
{
    SLE_LOGD("UnregisterConnnectCallback In");
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SLE_ASSERT_AND_RETURN_RET_LOG(
        pimpl_->callbacks_.Unregister(callback), SLE_ERR_FAILED, "unregister connect callback failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::DoConnect(Device &device) const
{
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SleErrorCode ret = pimpl_->DoConnect(device);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "do connect failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::DoConnectComplete(Device &device, const Message &msg) const
{
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SleErrorCode ret = pimpl_->DoConnectComplete(device, msg);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "do connect complete failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::DoDisconnect(Device &device) const
{
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SleErrorCode ret = pimpl_->DoDisconnect(device);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "do disconnect failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::DoDisconnectComplete(Device &device, const Message &msg) const
{
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SleErrorCode ret = pimpl_->DoDisconnectComplete(device, msg);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "do disconnect complete failed!");
    return SLE_NO_ERROR;
}

SleErrorCode SleConnectManager::DoUpdateConnectParam(Device &device, const Message &msg) const
{
    SLE_ASSERT_AND_RETURN_RET_LOG(pimpl_, SLE_ERR_NOT_READY, "pimpl null error!");
    SleErrorCode ret = pimpl_->DoUpdateConnectParam(device, msg);
    SLE_ASSERT_AND_RETURN_RET_LOG(ret == SLE_NO_ERROR, ret, "do update connect param failed!");
    return SLE_NO_ERROR;
}

void SleConnectManager::OnConnectStateChange(Device &device, SleConnectState state) const
{
    SLE_ASSERT_AND_RETURN_LOG(pimpl_, "pimpl null error!");
    pimpl_->OnConnectStateChange(device, state);
}

void SleConnectManager::OnConnectParamUpdateComplete(Device &device) const
{
    SLE_ASSERT_AND_RETURN_LOG(pimpl_, "pimpl null error!");
    pimpl_->OnConnectParamUpdateComplete(device);
}
} // SleStandard
} // NearLink
} // OHOS